# -*- coding: utf-8 -*-
from django.db import models
from django.db.models import F
from django.contrib.auth.models import User
import cPickle as pickle

class ArchiveAlreadyExists(Exception):
  pass

class SchoolYear(models.Model):
  """
  This model represents a school year.
  """
  name = models.CharField(u'Šolsko leto', unique = True, max_length = 9)
  start = models.DateField()
  end = models.DateField()
  archive = models.TextField(null = True)
  
  class Meta:
    ordering = ['-name']
    get_latest_by = 'name'
  
  def archive_year(self):
    """
    Archives current programs to this school year.
    """
    if self.archive and self.pk != SchoolYear.objects.latest().pk:
      raise ArchiveAlreadyExists
    
    programs = []
    for program in UniversityProgram.objects.all().order_by("id"):
      if not program.disabled:
        programs.append(program.archive())
    
    self.archive = pickle.dumps(programs)
    self.save()
  
  def get_archive(self):
    """
    Returns the program archive for this year.
    """
    return pickle.loads(str(self.archive))
  
  def __unicode__(self):
    """
    Returns a string representation of this model.
    """
    return u'%s' % self.name

class Subject(models.Model):
  """
  Represents a model that binds lecturers to subjects in a specific school year.
  """
  disabled = models.BooleanField(u'Šifra ni več veljavna', default = False)
  id = models.CharField(max_length = 5, verbose_name = u'Šifra', primary_key = True)
  name = models.CharField('Predmet', max_length = 50)
  lecturers = models.ManyToManyField(User, verbose_name = 'Izvajalci', related_name = 'lecture_subjects')
  current_lecturers = models.ManyToManyField(User, related_name = 'current_lecture_subjects')
  ects = models.IntegerField(default = 3)
  
  GRADE_MODE_CHOICES = (
    (1, 'Ocena izpita in vaj'),
    (2, 'Ocena vaj'),
    (3, 'Ocena izpita')
  )
  grade_mode = models.IntegerField(u'Tip ocene predmeta', default = 1, choices = GRADE_MODE_CHOICES)
  
  class Meta:
    ordering = ['name']
  
  def has_grade_tut(self):
    """
    Returns true if this subject has a tut grade.
    """
    return self.grade_mode in (1, 2)
  
  def has_grade_exam(self):
    """
    Returns true if this subject has an exam grade.
    """
    return self.grade_mode in (1, 3)
  
  def archive(self):
    """
    Creates an archive for this subject.
    """
    lecturers = []
    for lecturer in self.current_lecturers.all().order_by("pk"):
      lecturers.append(unicode(lecturer.get_profile()))
    
    return {
      'id'        : self.id,
      'name'      : unicode(self.name),
      'lecturers' : lecturers,
      'ects'      : self.ects
    }
  
  def __unicode__(self):
    """
    Returns a string representation of this model.
    """
    return u'(%s) %s' % (self.id, self.name)

class StudentSubjectMetadata(models.Model):
  """
  Represents student subject metadata.
  """
  student = models.ForeignKey(User)
  subject = models.ForeignKey(Subject)
  school_year = models.ForeignKey(SchoolYear, null = True)
  times_taken = models.IntegerField(default = 0)
  subtraction = models.BooleanField(default = False)

  @staticmethod
  def add_subtraction(student, subject):
    """
    Adds a subtraction.

    @param student: A valid student instance
    @param subject: A valid subject instance
    """
    try:
      meta = StudentSubjectMetadata.objects.get(student = student, subject = subject, subtraction = True)
      meta.times_taken = F('times_taken') + 1
    except StudentSubjectMetadata.DoesNotExist:
      meta = StudentSubjectMetadata(student = student, subject = subject, subtraction = True)
      meta.times_taken = 1

    meta.save()
  
  @staticmethod
  def recount(student, subject):
    """
    A convenience method for updating metadata.
    
    @param student: A valid student instance
    @param subject: A valid subject instance
    """
    from skoolmaster.exams.models import ExamApplication
    
    # Clear all previous metadata for this student/subject combo
    StudentSubjectMetadata.objects.filter(student = student, subject = subject, subtraction = False).delete()
    
    # Compute number of takes for each school year
    takes = ExamApplication.objects.filter(
      student = student,
      exam__subject = subject
    ).values('exam__school_year').annotate(num = models.Count('pk'))
    
    for take in takes:
      school_year = SchoolYear.objects.get(pk = take['exam__school_year'])
      m = StudentSubjectMetadata(student = student, subject = subject, school_year = school_year)
      m.times_taken = take['num']
      m.save()
    
    # Generate global count
    try:
      max_seq_no = ExamApplication.objects.filter(
        student = student,
        exam__subject = subject
      ).order_by('-seq_no').values_list('seq_no', flat = True)[0]
    except IndexError:
      max_seq_no = 0
    
    m = StudentSubjectMetadata(student = student, subject = subject, school_year = None)
    m.times_taken = max_seq_no
    m.save()
  
  @staticmethod
  def get_times_taken(student, subject, school_year = None, subtraction = False):
    """
    A convenience method for retrieving the number of times tha
    student has taken an exam for this subject.
    
    @param student: A valid student instance
    @param subject: A valid subject instance
    @param school_year: A valid school year or none for global count
    @return: Number of times the student has taken subject's exam
    """
    try:
      m = StudentSubjectMetadata.objects.get(student = student, subject = subject, school_year = school_year, subtraction = subtraction)
      return m.times_taken
    except StudentSubjectMetadata.DoesNotExist:
      return 0

class FacultyModule(models.Model):
  """
  This model represents a faculty module.
  """
  name = models.CharField('Ime modula', max_length = 50)
  required = models.BooleanField('Ali je modul obvezen?')
  subjects = models.ManyToManyField(Subject, verbose_name = 'Predmeti', related_name = 'faculty_modules', blank = True)

  def archive(self):
    """
    Creates an archive for this faculty module.
    """
    subjects = []
    for subject in self.subjects.all().order_by("name"):
      subjects.append(subject.archive())

    return {
      'name'      : unicode(self),
      'required'  : self.required,
      'subjects'  : subjects
    }
  
  def __unicode__(self):
    """
    Returns a string representation of this model.
    """
    return u'%s' % self.name

class FacultyYear(models.Model):
  """
  This model represents a faculty year.
  """
  name = models.CharField('Letnik', max_length = 50)
  modules = models.ManyToManyField(FacultyModule, verbose_name = 'Moduli', related_name = 'faculty_year', blank = True)
  elective_subjects = models.ManyToManyField(Subject, verbose_name = 'Izbirni predmeti', related_name = 'faculty_year', blank = True)
  
  class Meta:
    ordering = ['name']

  def archive(self):
    """
    Creates an archive for this faculty year.
    """
    modules = []
    for module in self.modules.all().order_by("name"):
      modules.append(module.archive())

    electives = []
    for subject in self.elective_subjects.all().order_by("name"):
      electives.append(subject.archive())

    return {
      'name'      : unicode(self),
      'modules'   : modules,
      'electives' : electives
    }
  
  def __unicode__(self):
    """
    Returns a string representation of this model.
    """
    return u'%s' % self.name

class UniversityProgram(models.Model):
  """
  Represents a university program.
  """
  disabled = models.BooleanField(u'Šifra ni več veljavna', default = False)
  id = models.CharField(max_length = 4, verbose_name = u'Šifra', primary_key = True)
  name = models.CharField('Ime programa', max_length = 50)
  faculty_years = models.ManyToManyField(FacultyYear, verbose_name = 'Letniki', related_name = 'university_programs')

  def archive(self):
    """
    Creates an archive for this university program.
    """
    years = []
    for year in self.faculty_years.all().order_by("name"):
      years.append(year.archive())

    return {
      'name'      : unicode(self),
      'id'        : self.id,
      'years'     : years  
    }

  def __unicode__(self):
    """
    Returns a string representation of this model.
    """
    return u'(%s) %s' % (self.id, self.name)
